/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */


import { ArkMethod, ArkFile, ArkAssignStmt, ArkStaticInvokeExpr } from "arkanalyzer/lib";
import Logger, { LOG_MODULE_TYPE } from 'arkanalyzer/lib/utils/logger';
import { BaseChecker, BaseMetaData } from "../BaseChecker";
import { Defects, IssueReport } from "../../model/Defects";
import { ClassMatcher, FileMatcher, MatcherCallback, MatcherTypes, MethodMatcher } from "../../matcher/Matchers";
import { CheckerUtils } from "../../utils/checker/CheckerUtils";
import { Rule } from "../../model/Rule";
import { RuleListUtil } from "../../utils/common/DefectsList";

type valueType = {
    name:string,
    ref:string,
    usedStmts:ArkAssignStmt[]
}
type RightOp = {
    yieldValue:ArkStaticInvokeExpr
}
const logger = Logger.getLogger(LOG_MODULE_TYPE.HOMECHECK, 'RequireAwaitCheck');
const gmetaData: BaseMetaData = {
    severity: 2,
    ruleDocPath: "docs/require-await-check.md", // TODO: support url
    description: "Disallow async functions which have no `await` expression."
};
export class RequireAwaitCheck implements BaseChecker {
    readonly metaData: BaseMetaData = gmetaData;
    public rule: Rule;
    public defects: Defects[] = [];
    public issues: IssueReport[] = [];
    private fileMatcher: FileMatcher = {
        matcherType: MatcherTypes.FILE,
    };

    private classMatcher: ClassMatcher = {
        file: [this.fileMatcher],
        matcherType: MatcherTypes.CLASS
    };
    private methodMatcher: MethodMatcher = {
        matcherType: MatcherTypes.METHOD,
        class: [this.classMatcher]
    };
    public registerMatchers(): MatcherCallback[] {
        const matchMethod: MatcherCallback = {
            matcher: this.methodMatcher,
            callback: this.check
        }
        return [matchMethod];
    }
    /**
     * 判断字符串中是否包含await关键字
     *
     * @param text 要判断的字符串
     * @returns 如果字符串中包含await关键字，则返回true；否则返回false
     */
    private containsAwaitKeyword(text: string) {
        return /await\b/.test(text);
    }
    /**
     * 判断给定的文本中是否包含异步关键字 'async'
     *
     * @param text 待检查的文本
     * @returns 如果文本中包含 'async' 关键字，则返回 true，否则返回 false
     */
    private containsAsyncKeyword(text: string) {
        return /async\b/.test(text);
    }
    /**
     * 检查文本中是否包含 "catch" 关键字
     *
     * @param text 需要检查的文本
     * @returns 如果文本中包含 "catch" 关键字，则返回 true，否则返回 false
     */
    private containsCatchKeyword(text: string) {
        return /catch\b/.test(text);
    }
    /**
     * 判断字符串中是否包含 'then' 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果字符串中包含 'then' 关键字，则返回 true；否则返回 false
     */
    private containsThenKeyword(text: string) {
        return /then\b/.test(text);
    }
    /**
     * 判断字符串中是否包含关键词 "resolve"
     *
     * @param text 待判断的字符串
     * @returns 如果字符串中包含关键词 "resolve"，则返回 true，否则返回 false
     */
    private containsResolveKeyword(text: string) {
        return /resolve\b/.test(text);
    }
    /**
     * 检查字符串中是否包含拒绝关键字
     *
     * @param text 待检查的字符串
     * @returns 如果包含拒绝关键字则返回 true，否则返回 false
     */
    private containsRejectKeyword(text: string) {
        return /reject\b/.test(text);
    }
    /**
     * 检查字符串中是否包含 "Promise" 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果字符串中包含 "Promise" 关键字，则返回 true；否则返回 false
     */
    private containsPromiseKeyword(text: string) {
        return /Promise\b/.test(text);
    }
    /**
     * 判断给定的字符串中是否包含"unknown" 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果包含未知的关键词，则返回 true；否则返回 false
     */
    private containsUnknownKeyword(text: string) {
        return /unknown\b/.test(text);
    }
    /**
     * 判断字符串中是否包含 'void' 关键字
     *
     * @param text 要检查的字符串
     * @returns 如果包含 'void' 关键字，则返回 true；否则返回 false
     */
    private containsVoidKeyword(text: string) {
        return /void\b/.test(text);
    }
    public check = (method: ArkMethod) => {
        // 获取方法体的语句列表
        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
        // 获取方法的代码
        const code = method.getCode() ?? '';
        // 获取方法所属的类
        let declareClass = method.getDeclaringArkClass()
        // 获取类所属的文件
        const arkFile = declareClass.getDeclaringArkFile();
        // 初始化异步文本变量
        let asyncText = ''
        // 检查函数体是否为空或者是否包含过多的语句，如果是则直接返回
        if (stmts.length === 0 || stmts.length >= 10) {
            return;
        }
        //如果代码中不包含async关键字，则直接返回
        if (!this.containsAsyncKeyword(code)) {
            return;
        }
        for (const stmt of stmts) {
            // 从语句中获取调用表达式
            const invokeExpr = CheckerUtils.getInvokeExprFromStmt(stmt)
            if (!invokeExpr) {
                // 如果代码中包含async关键字，则记录异步文本
                if (this.containsAsyncKeyword(code)) {
                    asyncText = code;
                }
                // 如果存在异步文本，则进行正则匹配
                if (asyncText) {
                    const regex = /\basync\s+([^(]+)/g;
                    const regex1 = /async\s*\(\)(?::\s*.*?)?\s*=>/;
                    const match1 = asyncText.match(regex1);
                    const match = asyncText.match(regex);
                    const methodName = method.getName();
                    if (match) {
                        const asyncFunction = match[0];
                        let text = stmt.getOriginalText() ?? '';
                        // 如果语句文本存在且不包含await关键字
                        if (text && !this.containsAwaitKeyword(text)) {
                            // 如果语句文本不包含catch、then、reject、resolve关键字
                            if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                                // 如果语句文本不包含reject,resolve关键字
                                if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                    const regex = /return\s+\w+\(.*?\)/;
                                    if (regex.test(text.trim())) {
                                        continue;
                                    }else if(text.includes('return ')){
                                        if(text.includes('Promise')){
                                            continue;
                                        }
                                        this.metaData.description = `Async function ${methodName} has no 'await' expression.`;
                                        this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                    }
                                    if (!text.includes('return ') || text.includes('await ')) {
                                        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
                                        let flag = true;
                                        for(const stmt of stmts) {
                                            const text = stmt.getOriginalText() ?? '';
                                            if(this.containsAwaitKeyword(text)){
                                                flag = false;
                                            }
                                        }
                                        const usedGlobals = method.getBody()?.getUsedGlobals()??[];
                                        for(const [key,value] of usedGlobals?.entries()){
                                            const Value = value as unknown as valueType;
                                            const usedStmts = Value?.usedStmts??[];
                                            for(const usedStmt of usedStmts){
                                                if(usedStmt instanceof ArkAssignStmt){
                                                    const rightOp = usedStmt.getRightOp() as unknown as RightOp;
                                                    const methodName = rightOp?.yieldValue?.getMethodSignature()?.getMethodSubSignature()?.getMethodName()??'';
                                                    if(methodName !== ''){
                                                        flag = false;
                                                    }
                                                }
                                            }
                                        }
                                        const parameters = method.getSignature()?.getMethodSubSignature()?.getParameters()??[];
                                        for(const parameter of parameters){
                                            const parameterName = parameter.getName();
                                            if(text.includes(parameterName)){
                                                flag = false;
                                            }
                                        }
                                        if(text.includes('Omit')){
                                            flag = false;
                                        }
                                        for(const stmt of stmts) {
                                            if(stmt instanceof ArkAssignStmt){
                                                const leftOp = stmt.getLeftOp()?.toString();
                                                if(leftOp == 'this' || leftOp.startsWith('%')){
                                                    continue;
                                                }
                                                if(text.includes(leftOp)){
                                                    flag = false;
                                                }
                                            }
                                        }
                                        if(flag == true){
                                            this.metaData.description = `Async function ${methodName} has no 'await' expression.`;
                                            this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                        }
                                    }
                                }
                            }
                        }
                        const locals = method.getBody()?.getLocals()??[];
                        for(const [key,value] of locals.entries()){
                            const keyName = key.toString();
                            if(keyName == 'this'){
                                continue;
                            }
                            const usedStmts = value.getUsedStmts()??[];
                            if(usedStmts.length > 0){
                                const valueName = value.getName();
                                if(valueName.includes(methodName)){
                                    for(const usedStmt of usedStmts){
                                        const text = usedStmt.getOriginalText()??'';
                                        if(!text.startsWith('await')){
                                            this.metaData.description = `Async function ${methodName} has no 'await' expression.`;
                                            this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                        }else{
                                            const regex = /await\s*\(\s*async\s*\(\)(?::\s*.*?)?\s*=>/;
                                            const match = text.match(regex);
                                            if(match){
                                                const target = match[0];
                                                this.metaData.description = "Async arrow function has no 'await' expression.";
                                                this.addIssueReport(arkFile, target,this.metaData);
                                            }
                                        }
                                    }
                                }
                            }
                        }
                        const methods = declareClass.getMethods();
                        if(methods.length > 2){
                            for(const method of methods){
                                const methodCode = method.getCode();
                                if(!methodCode){
                                    continue;
                                }
                                const MethodName = method.getSignature()?.getMethodSubSignature()?.getMethodName()??'';
                                if(MethodName.includes('%') && MethodName.includes(methodName)){
                                    if(!methodCode.startsWith('await')){
                                        const regex = /async\s*\(\)(?::\s*.*?)?\s*=>/;
                                        const match = methodCode.match(regex);
                                        if(match){
                                            const target = match[0];
                                            this.metaData.description = "Async arrow function has no 'await' expression.";
                                            this.addIssueReport(arkFile, target,this.metaData);
                                        }else{
                                            this.metaData.description = `Async function ${methodName} has no 'await' expression.`;
                                            this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                        }
                                    }
                                }
                            }
                        }
                    }
                    if(match1){
                        const asyncFunction = match1.input;
                        let text = stmt.getOriginalText() ?? '';
                        // 如果语句文本存在且不包含await关键字
                        if (text && !this.containsAwaitKeyword(text)) {
                            // 如果语句文本不包含catch、then、reject、resolve关键字
                            if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                                // 如果语句文本不包含reject,resolve关键字
                                if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                    this.metaData.description = "Async arrow function has no 'await' expression."
                                    if(asyncFunction){
                                        this.addIssueReport(arkFile,asyncFunction,this.metaData);
                                    }
                                    
                                }
                            }
                        }
                    }
                }
                continue;
            }
            if (this.containsAsyncKeyword(code)) {
                asyncText = code;
            }
            if (asyncText) {
                const regex = /\basync\s+([^(]+)/g;
                const match = asyncText.match(regex);
                const methodName = method.getName();
                if (match) {
                    const asyncFunction = match[0];
                    // 获取调用表达式的方法签名
                    const methodSign = invokeExpr.getMethodSignature();
                    // 获取方法签名的子签名
                    const subSignature = methodSign.getMethodSubSignature();
                    // 获取子签名的返回类型
                    const returnType = subSignature.getReturnType();
                    // 获取返回类型的字符串表示
                    const returnTypeString = returnType.getTypeString();
                    let text = stmt.getOriginalText() ?? '';
                    // 如果语句文本不包含await关键字
                    if (!this.containsAwaitKeyword(text)) {
                        // 如果语句文本不包含catch、then、reject、resolve关键字
                        if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                            if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                // 如果返回类型包含Promise、Unknown或Void关键字
                                if (this.containsPromiseKeyword(returnTypeString) || this.containsUnknownKeyword(returnTypeString) || this.containsVoidKeyword(returnTypeString)) {
                                    if (!text.includes('return ') || text.includes('await ')) {
                                        const stmts = method.getBody()?.getCfg().getStmts() ?? [];
                                        let flag = true;
                                        for(const stmt of stmts) {
                                            const text = stmt.getOriginalText() ?? '';
                                            if(this.containsAwaitKeyword(text)){
                                                flag = false;
                                            }
                                        }
                                        const usedGlobals = method.getBody()?.getUsedGlobals()??[];
                                        for(const [key,value] of usedGlobals?.entries()){
                                            const Value = value as unknown as valueType;
                                            const usedStmts = Value?.usedStmts??[];
                                            for(const usedStmt of usedStmts){
                                                if(usedStmt instanceof ArkAssignStmt){
                                                    const rightOp = usedStmt.getRightOp() as unknown as RightOp;
                                                    const methodName = rightOp?.yieldValue?.getMethodSignature()?.getMethodSubSignature()?.getMethodName()??'';
                                                    if(methodName !== ''){
                                                        flag = false;
                                                    }
                                                }
                                            }
                                        }
                                        if(flag == true){
                                            this.metaData.description = `Async function ${methodName} has no 'await' expression.`;
                                            this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if(asyncText){
                const methodName = method.getSignature()?.getMethodSubSignature()?.getMethodName()??'';
                if(methodName.includes('build')){
                    const regex = /=>/;
                    const match = asyncText.match(regex);
                    if(match){
                        const asyncFunction = match[0];
                        // 获取调用表达式的方法签名
                        const methodSign = invokeExpr.getMethodSignature();
                        // 获取方法签名的子签名
                        const subSignature = methodSign.getMethodSubSignature();
                        // 获取子签名的返回类型
                        const returnType = subSignature.getReturnType();
                        // 获取返回类型的字符串表示
                        const returnTypeString = returnType.getTypeString();
                        let text = stmt.getOriginalText() ?? '';
                        // 如果语句文本不包含await关键字
                        if (!this.containsAwaitKeyword(text)) {
                            // 如果语句文本不包含catch、then、reject、resolve关键字
                            if (!this.containsCatchKeyword(text) && !this.containsThenKeyword(text)) {
                                if (!this.containsRejectKeyword(text) && !this.containsResolveKeyword(text)) {
                                    // 如果返回类型包含Promise、Unknown或Void关键字
                                    if (this.containsPromiseKeyword(returnTypeString) || this.containsUnknownKeyword(returnTypeString) || this.containsVoidKeyword(returnTypeString)) {
                                        if (!text.includes('return ') || text.includes('await ')) {
                                            this.metaData.description = "Async arrow function has no 'await' expression."
                                            this.addIssueReport(arkFile, asyncFunction,this.metaData);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    private addIssueReport(arkFile: ArkFile, name: string,metaData: BaseMetaData) {
        const severity = this.rule.alert ?? metaData.severity;
        const warnInfo = this.getLineAndColumn(arkFile, name);
        this.metaData.description = metaData.description.padEnd(130);
        const filePath = arkFile.getFilePath();
        let defects = new Defects(warnInfo.line, warnInfo.startCol, warnInfo.endCol, metaData.description, severity, this.rule.ruleId, filePath,
            metaData.ruleDocPath, true, false, false);
        this.issues.push(new IssueReport(defects, undefined));
        RuleListUtil.push(defects);
    }
    private getLineAndColumn(arkFile: ArkFile, name: string) {
        // 判断arkFile是否存在
        if (arkFile) {
            // 获取arkFile的代码
            const code = arkFile.getCode() ?? '';
            // 将代码按行分割
            const lines = code.split('\r\n');
            let lineNumber = 1;
            // 遍历每一行代码
            for (const line of lines) {
                // 在当前行中查找name的索引
                const lineIndex = line.indexOf(name);
                if (lineIndex !== -1) {
                    if(name.includes('=>')){
                        const LineIndex = line.indexOf('=>');
                        const startCol = LineIndex + 1; // 列号从1开始
                        const endCol = LineIndex + 1;
                        // 获取arkFile的真实路径
                        const originPath = arkFile.getFilePath();
                        // 返回包含行号、列号和文件路径的对象
                        return { line: lineNumber, startCol, endCol, filePath: originPath }
                    }else{
                        const startCol = lineIndex + 1; // 列号从1开始
                        const endCol = lineIndex + 1;
                        // 获取arkFile的真实路径
                        const originPath = arkFile.getFilePath();
                        // 返回包含行号、列号和文件路径的对象
                        return { line: lineNumber, startCol, endCol, filePath: originPath }
                    }
                }
                // 行号递增
                lineNumber++;
            }
        } else {
            // 如果arkFile不存在，记录日志
            logger.debug('originStmt or arkFile is null');
        }
        // 返回默认的对象，表示未找到name
        return { line: -1, startCol: -1, endCol: -1, filePath: '' };
    }
}